/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.hop.workflow.actions.checkfilelocked;

import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSelectInfo;
import org.apache.commons.vfs2.FileSelector;
import org.apache.commons.vfs2.FileType;
import org.apache.hop.core.Const;
import org.apache.hop.core.ICheckResult;
import org.apache.hop.core.Result;
import org.apache.hop.core.RowMetaAndData;
import org.apache.hop.core.annotations.Action;
import org.apache.hop.core.exception.HopException;
import org.apache.hop.core.exception.HopXmlException;
import org.apache.hop.core.util.Utils;
import org.apache.hop.core.variables.IVariables;
import org.apache.hop.core.vfs.HopVfs;
import org.apache.hop.core.xml.XmlHandler;
import org.apache.hop.i18n.BaseMessages;
import org.apache.hop.metadata.api.IHopMetadataProvider;
import org.apache.hop.workflow.WorkflowMeta;
import org.apache.hop.workflow.action.ActionBase;
import org.apache.hop.workflow.action.IAction;
import org.apache.hop.workflow.action.validator.AbstractFileValidator;
import org.apache.hop.workflow.action.validator.ActionValidatorUtils;
import org.apache.hop.workflow.action.validator.AndValidator;
import org.apache.hop.workflow.action.validator.ValidatorContext;
import org.w3c.dom.Node;

import java.io.IOException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/** This defines a 'check files locked' action. */
@Action(
    id = "CHECK_FILES_LOCKED",
    name = "i18n::ActionCheckFilesLocked.Name",
    description = "i18n::ActionCheckFilesLocked.Description",
    image = "CheckFilesLocked.svg",
    categoryDescription = "i18n:org.apache.hop.workflow:ActionCategory.Category.Conditions",
    keywords = "i18n::ActionCheckFilesLocked.keyword",
    documentationUrl = "/workflow/actions/checkfilelocked.html")
public class ActionCheckFilesLocked extends ActionBase implements Cloneable, IAction {
  private static final Class<?> PKG = ActionCheckFilesLocked.class; // For Translator

  public boolean argFromPrevious;

  public boolean includeSubfolders;

  public String[] arguments;

  public String[] filemasks;

  private boolean oneFileLocked;

  public ActionCheckFilesLocked(String n) {
    super(n, "");
    argFromPrevious = false;
    arguments = null;

    includeSubfolders = false;
  }

  public ActionCheckFilesLocked() {
    this("");
  }

  @Override
  public Object clone() {
    ActionCheckFilesLocked je = (ActionCheckFilesLocked) super.clone();
    if (arguments != null) {
      int nrFields = arguments.length;
      je.allocate(nrFields);
      System.arraycopy(arguments, 0, je.arguments, 0, nrFields);
      System.arraycopy(filemasks, 0, je.filemasks, 0, nrFields);
    }
    return je;
  }

  public void allocate(int nrFields) {
    arguments = new String[nrFields];
    filemasks = new String[nrFields];
  }

  @Override
  public String getXml() {
    StringBuilder xml = new StringBuilder(300);

    xml.append(super.getXml());
    xml.append("      ").append(XmlHandler.addTagValue("arg_from_previous", argFromPrevious));
    xml.append("      ").append(XmlHandler.addTagValue("include_subfolders", includeSubfolders));

    xml.append("      <fields>").append(Const.CR);
    if (arguments != null) {
      for (int i = 0; i < arguments.length; i++) {
        xml.append("        <field>").append(Const.CR);
        xml.append("          ").append(XmlHandler.addTagValue("name", arguments[i]));
        xml.append("          ").append(XmlHandler.addTagValue("filemask", filemasks[i]));
        xml.append("        </field>").append(Const.CR);
      }
    }
    xml.append("      </fields>").append(Const.CR);

    return xml.toString();
  }

  @Override
  public void loadXml(Node entrynode, IHopMetadataProvider metadataProvider, IVariables variables)
      throws HopXmlException {
    try {
      super.loadXml(entrynode);
      argFromPrevious =
          "Y".equalsIgnoreCase(XmlHandler.getTagValue(entrynode, "arg_from_previous"));
      includeSubfolders =
          "Y".equalsIgnoreCase(XmlHandler.getTagValue(entrynode, "include_subfolders"));

      Node fields = XmlHandler.getSubNode(entrynode, "fields");

      // How many field arguments?
      int nrFields = XmlHandler.countNodes(fields, "field");
      allocate(nrFields);

      // Read them all...
      for (int i = 0; i < nrFields; i++) {
        Node fnode = XmlHandler.getSubNodeByNr(fields, "field", i);

        arguments[i] = XmlHandler.getTagValue(fnode, "name");
        filemasks[i] = XmlHandler.getTagValue(fnode, "filemask");
      }
    } catch (HopXmlException xe) {
      throw new HopXmlException(
          BaseMessages.getString(PKG, "ActionCheckFilesLocked.UnableToLoadFromXml"), xe);
    }
  }

  @Override
  public Result execute(Result previousResult, int nr) {

    Result result = previousResult;
    List<RowMetaAndData> rows = result.getRows();
    RowMetaAndData resultRow = null;

    oneFileLocked = false;
    result.setResult(true);

    try {
      if (argFromPrevious) {
        if (isDetailed()) {
          logDetailed(
              BaseMessages.getString(
                  PKG,
                  "ActionCheckFilesLocked.FoundPreviousRows",
                  String.valueOf((rows != null ? rows.size() : 0))));
        }
      }

      if (argFromPrevious && rows != null) {
        // Copy the input row to the (command line) arguments
        for (int iteration = 0;
            iteration < rows.size() && !parentWorkflow.isStopped();
            iteration++) {
          resultRow = rows.get(iteration);

          // Get values from previous result
          String fileFolderPrevious = resultRow.getString(0, "");
          String fileMasksPrevious = resultRow.getString(1, "");

          // ok we can process this file/folder
          if (isDetailed()) {
            logDetailed(
                BaseMessages.getString(
                    PKG,
                    "ActionCheckFilesLocked.ProcessingRow",
                    fileFolderPrevious,
                    fileMasksPrevious));
          }

          ProcessFile(fileFolderPrevious, fileMasksPrevious);
        }
      } else if (arguments != null) {

        for (int i = 0; i < arguments.length && !parentWorkflow.isStopped(); i++) {
          // ok we can process this file/folder
          if (isDetailed()) {
            logDetailed(
                BaseMessages.getString(
                    PKG, "ActionCheckFilesLocked.ProcessingArg", arguments[i], filemasks[i]));
          }

          ProcessFile(arguments[i], filemasks[i]);
        }
      }

      if (oneFileLocked) {
        result.setResult(false);
        result.setNrErrors(1);
      }
    } catch (Exception e) {
      logError(BaseMessages.getString(PKG, "ActionCheckFilesLocked.ErrorRunningAction", e));
    }

    return result;
  }

  private void ProcessFile(String filename, String wildcard) {

    FileObject filefolder = null;
    String realFilefoldername = resolve(filename);
    String realwilcard = resolve(wildcard);

    try {
      filefolder = HopVfs.getFileObject(realFilefoldername);
      FileObject[] files = new FileObject[] {filefolder};
      if (filefolder.exists()) {
        // the file or folder exists
        if (filefolder.getType() == FileType.FOLDER) {
          // It's a folder
          if (isDetailed()) {
            logDetailed(
                BaseMessages.getString(
                    PKG, "ActionCheckFilesLocked.ProcessingFolder", realFilefoldername));
          }
          // Retrieve all files
          files = filefolder.findFiles(new TextFileSelector(filefolder.toString(), realwilcard));

          if (isDetailed()) {
            logDetailed(
                BaseMessages.getString(
                    PKG, "ActionCheckFilesLocked.TotalFilesToCheck", String.valueOf(files.length)));
          }
        } else {
          // It's a file
          if (isDetailed()) {
            logDetailed(
                BaseMessages.getString(
                    PKG, "ActionCheckFilesLocked.ProcessingFile", realFilefoldername));
          }
        }
        // Check files locked
        checkFilesLocked(files);
      } else {
        // We can not find thsi file
        logBasic(
            BaseMessages.getString(PKG, "ActionCheckFilesLocked.FileNotExist", realFilefoldername));
      }
    } catch (Exception e) {
      logError(
          BaseMessages.getString(
              PKG, "ActionCheckFilesLocked.CouldNotProcess", realFilefoldername, e.getMessage()));
    } finally {
      if (filefolder != null) {
        try {
          filefolder.close();
        } catch (IOException ex) {
          // Ignore
        }
      }
    }
  }

  private void checkFilesLocked(FileObject[] files) throws HopException {

    for (int i = 0; i < files.length && !oneFileLocked; i++) {
      FileObject file = files[i];
      String filename = HopVfs.getFilename(file);
      LockFile locked = new LockFile(filename);
      if (locked.isLocked()) {
        oneFileLocked = true;
        logError(BaseMessages.getString(PKG, "ActionCheckFilesLocked.Log.FileLocked", filename));
      } else {
        if (isDetailed()) {
          logDetailed(
              BaseMessages.getString(PKG, "ActionCheckFilesLocked.Log.FileNotLocked", filename));
        }
      }
    }
  }

  private class TextFileSelector implements FileSelector {
    String fileWildcard = null;
    String sourceFolder = null;

    public TextFileSelector(String sourcefolderin, String filewildcard) {

      if (!Utils.isEmpty(sourcefolderin)) {
        sourceFolder = sourcefolderin;
      }

      if (!Utils.isEmpty(filewildcard)) {
        fileWildcard = filewildcard;
      }
    }

    @Override
    public boolean includeFile(FileSelectInfo info) {
      boolean returncode = false;
      FileObject filename = null;
      try {

        if (!info.getFile().toString().equals(sourceFolder)) {
          // Pass over the Base folder itself

          String shortFilename = info.getFile().getName().getBaseName();

          if (!info.getFile().getParent().equals(info.getBaseFolder())) {

            // Not in the Base Folder..Only if include sub folders
            if (includeSubfolders
                && (info.getFile().getType() == FileType.FILE)
                && GetFileWildcard(shortFilename, fileWildcard)) {
              if (isDetailed()) {
                logDetailed(
                    BaseMessages.getString(
                        PKG, "ActionCheckFilesLocked.CheckingFile", info.getFile().toString()));
              }

              returncode = true;
            }
          } else {
            // In the Base Folder...

            if ((info.getFile().getType() == FileType.FILE)
                && GetFileWildcard(shortFilename, fileWildcard)) {
              if (isDetailed()) {
                logDetailed(
                    BaseMessages.getString(
                        PKG, "ActionCheckFilesLocked.CheckingFile", info.getFile().toString()));
              }

              returncode = true;
            }
          }
        }

      } catch (Exception e) {
        logError(
            BaseMessages.getString(PKG, "ActionCheckFilesLocked.Error.Exception.ProcessError"),
            BaseMessages.getString(
                PKG,
                "JobCheckFilesLocked.Error.Exception.Process",
                info.getFile().toString(),
                e.getMessage()));
        returncode = false;
      } finally {
        if (filename != null) {
          try {
            filename.close();

          } catch (IOException ex) {
            /* Ignore */
          }
        }
      }

      return returncode;
    }

    @Override
    public boolean traverseDescendents(FileSelectInfo info) {
      return info.getDepth() == 0 || includeSubfolders;
    }
  }

  /**********************************************************
   *
   * @param selectedfile
   * @param wildcard
   * @return True if the selectedfile matches the wildcard
   **********************************************************/
  private boolean GetFileWildcard(String selectedfile, String wildcard) {
    Pattern pattern = null;
    boolean getIt = true;

    if (!Utils.isEmpty(wildcard)) {
      pattern = Pattern.compile(wildcard);
      // First see if the file matches the regular expression!
      if (pattern != null) {
        Matcher matcher = pattern.matcher(selectedfile);
        getIt = matcher.matches();
      }
    }

    return getIt;
  }

  public void setIncludeSubfolders(boolean includeSubfolders) {
    this.includeSubfolders = includeSubfolders;
  }

  public void setargFromPrevious(boolean argFromPrevious) {
    this.argFromPrevious = argFromPrevious;
  }

  @Override
  public boolean isEvaluation() {
    return true;
  }

  public boolean isArgFromPrevious() {
    return argFromPrevious;
  }

  public String[] getArguments() {
    return arguments;
  }

  public String[] getFilemasks() {
    return filemasks;
  }

  public boolean isIncludeSubfolders() {
    return includeSubfolders;
  }

  @Override
  public void check(
      List<ICheckResult> remarks,
      WorkflowMeta workflowMeta,
      IVariables variables,
      IHopMetadataProvider metadataProvider) {
    boolean res =
        ActionValidatorUtils.andValidator()
            .validate(
                this,
                "arguments",
                remarks,
                AndValidator.putValidators(ActionValidatorUtils.notNullValidator()));

    if (res == false) {
      return;
    }

    ValidatorContext ctx = new ValidatorContext();
    AbstractFileValidator.putVariableSpace(ctx, getVariables());
    AndValidator.putValidators(
        ctx, ActionValidatorUtils.notNullValidator(), ActionValidatorUtils.fileExistsValidator());

    for (int i = 0; i < arguments.length; i++) {
      ActionValidatorUtils.andValidator().validate(this, "arguments[" + i + "]", remarks, ctx);
    }
  }
}
